﻿// Deep Earth is a community project available under the Microsoft Public License (Ms-PL)
// Code is provided as is and with no warranty – Use at your own risk
// View the project and the latest code at http://DeepEarth.codeplex.com/

using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Collections.Generic;
using DeepEarth.Core.Persistence;

namespace DeepEarth.Toolkit.Controls
{
	[TemplatePart(Name = PART_Chrome, Type = typeof(FrameworkElement))]
	[TemplatePart(Name = PART_CloseButton, Type = typeof(ButtonBase))]
	[TemplatePart(Name = PART_ContentRoot, Type = typeof(FrameworkElement))]
	[TemplatePart(Name = PART_Root, Type = typeof(Canvas))]
	[TemplatePart(Name = PART_Resizer, Type = typeof(FrameworkElement))]
	[TemplateVisualState(Name = VSMSTATE_StateClosed, GroupName = VSMGROUP_Window)]
	[TemplateVisualState(Name = VSMSTATE_StateOpen, GroupName = VSMGROUP_Window)]
	[TemplateVisualState(Name = VSMSTATE_StateMouseOver, GroupName = VSMGROUP_Resizer)]
	[TemplateVisualState(Name = VSMSTATE_StateNormal, GroupName = VSMGROUP_Resizer)]
	public class FloatingPanel : ContentControl
	{
		private const string PART_Chrome = "PART_Chrome";
		private const string PART_CloseButton = "PART_CloseButton";
		private const string PART_ContentRoot = "PART_ContentRoot";
		private const string PART_Resizer = "PART_Resizer";
		private const string PART_Root = "PART_Root";
		private const string VSMGROUP_Resizer = "ResizerStates";
		private const string VSMGROUP_Window = "WindowStates";
		private const string VSMSTATE_StateClosed = "Closed";
		private const string VSMSTATE_StateMouseOver = "MouseOver";
		private const string VSMSTATE_StateNormal = "Normal";
		private const string VSMSTATE_StateOpen = "Open";

		private FrameworkElement contentRoot;
		private Canvas root;

        private List<Action> InitializationActions = new List<Action>();

		public FloatingPanel()
		{
			DefaultStyleKey = typeof(FloatingPanel);

            this.SizeChanged += new SizeChangedEventHandler(FloatingPanel_SizeChanged);
		}

        void FloatingPanel_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            foreach (Action a in InitializationActions)
            {
                a();
            }

            InitializationActions.Clear();

            this.SizeChanged -= FloatingPanel_SizeChanged;
        }

        private void Arrange()
        {
            Snap();
			ValidateLocation(); 
			AutoSnap();
        }

		#region SnapPosition

		public SnapPosition SnapPosition
		{
			get { return snapPosition; }
			set
			{
				if (snapPosition == value)
					return;

				snapPosition = value;
				Snap();

				if (SnapPositionChanged != null)
					SnapPositionChanged(this, new EventArgs());
			}
		}
		private void Snap()
		{
			var maxx = double.MaxValue;
			var maxy = double.MaxValue;
            if (contentRoot != null && contentRoot.ActualWidth > 0)
            {
                maxx = root.ActualWidth - contentRoot.ActualWidth;
                maxy = root.ActualHeight - contentRoot.ActualHeight;

                switch (SnapPosition)
                {
                    case SnapPosition.Bottom:
                        Location = new Point(Location.X, maxy);
                        break;
                    case SnapPosition.BottomLeft:
                        Location = new Point(0, maxy);
                        break;
                    case SnapPosition.BottomRight:
                        Location = new Point(maxx, maxy);
                        break;
                    case SnapPosition.Top:
                        Location = new Point(Location.X, 0);
                        break;
                    case SnapPosition.TopLeft:
                        Location = new Point(0, 0);
                        break;
                    case SnapPosition.TopRight:
                        Location = new Point(maxx, 0);
                        break;
                    case SnapPosition.Left:
                        Location = new Point(0, Location.Y);
                        break;
                    case SnapPosition.Right:
                        Location = new Point(maxx, Location.Y);
                        break;
                    default:
                        return;
                }
            }
		}
		private SnapPosition snapPosition = SnapPosition.None;

		public event EventHandler SnapPositionChanged;

		#endregion

		#region Location

		public Point Location
		{
			get
			{
				return (Point)GetValue(LocationProperty);
			}

			set
			{
				SetValue(LocationProperty, value);
			}
		}

		/// <summary>
		/// Location dependency property.
		/// </summary>
		public static readonly DependencyProperty LocationProperty =
			DependencyProperty.Register(
				"Location",
				typeof(Point),
				typeof(FloatingPanel),
				new PropertyMetadata(new Point(), (d, e) => ((FloatingPanel)d).OnLocationChanged((Point)e.OldValue, (Point)e.NewValue)));

		/// <summary>
		/// handles the LocationProperty changes.
		/// </summary>
		/// <param name="oldValue">The old value.</param>
		/// <param name="newValue">The new value.</param>
		private void OnLocationChanged(Point oldValue, Point newValue)
		{
			ValidateLocation();
			AutoSnap();
		}

		void ValidateLocation()
		{
			if (contentRoot == null || root == null)
				return;

			var value = Location;
			if (value.X >= root.ActualWidth - contentRoot.ActualWidth)
				value.X = root.ActualWidth - contentRoot.ActualWidth;
			if (value.Y >= root.ActualHeight - contentRoot.ActualHeight)
				value.Y = root.ActualHeight - contentRoot.ActualHeight;
			if (value.X <= 0)
				value.X = 0;
			if (value.Y <= 0)
				value.Y = 0;
			Location = value;
		}

		void AutoSnap()
		{
			if (contentRoot == null || root == null || contentRoot.ActualWidth == 0 || root.ActualWidth == 0)
				return;

			var snap = SnapPosition.None;
			var value = Location;
			if (value.X >= root.ActualWidth - contentRoot.ActualWidth - 5)
				snap |= SnapPosition.Right;
			if (value.Y >= root.ActualHeight - contentRoot.ActualHeight - 5)
				snap |= SnapPosition.Bottom;
			if (value.X <= 5)
				snap |= SnapPosition.Left;
			if (value.Y <= 5)
				snap |= SnapPosition.Top;

			SnapPosition = snap;
		}

		#endregion

		#region Title

		public object Title
		{
			get
			{
				return (object)GetValue(TitleProperty);
			}

			set
			{
				SetValue(TitleProperty, value);
			}
		}

		/// <summary>
		/// Title dependency property.
		/// </summary>
		public static readonly DependencyProperty TitleProperty =
			DependencyProperty.Register(
				"Title",
				typeof(object),
				typeof(FloatingPanel),
				new PropertyMetadata((d, e) => ((FloatingPanel)d).OnTitleChanged((object)e.OldValue, (object)e.NewValue)));

		/// <summary>
		/// handles the TitleProperty changes.
		/// </summary>
		/// <param name="oldValue">The old value.</param>
		/// <param name="newValue">The new value.</param>
		private void OnTitleChanged(object oldValue, object newValue)
		{

		}

		#endregion Title

		#region EnableResizer

		public bool EnableResizer
		{
			get
			{
				return (bool)GetValue(EnableResizerProperty);
			}

			set
			{
				SetValue(EnableResizerProperty, value);
			}
		}

		/// <summary>
		/// EnableResizer dependency property.
		/// </summary>
		public static readonly DependencyProperty EnableResizerProperty =
			DependencyProperty.Register(
				"EnableResizer",
				typeof(bool),
				typeof(FloatingPanel),
				new PropertyMetadata(true, (d, e) => ((FloatingPanel)d).OnEnableResizerChanged((bool)e.OldValue, (bool)e.NewValue)));

		/// <summary>
		/// handles the EnableResizerProperty changes.
		/// </summary>
		/// <param name="oldValue">The old value.</param>
		/// <param name="newValue">The new value.</param>
		private void OnEnableResizerChanged(bool oldValue, bool newValue)
		{

		}

		#endregion EnableResizer

		#region EnableCloseButton

		public bool EnableCloseButton
		{
			get
			{
				return (bool)GetValue(EnableCloseButtonProperty);
			}

			set
			{
				SetValue(EnableCloseButtonProperty, value);
			}
		}

		/// <summary>
		/// EnableCloseButton dependency property.
		/// </summary>
		public static readonly DependencyProperty EnableCloseButtonProperty =
			DependencyProperty.Register(
				"EnableCloseButton",
				typeof(bool),
				typeof(FloatingPanel),
				new PropertyMetadata(true, (d, e) => ((FloatingPanel)d).OnEnableCloseButtonChanged((bool)e.OldValue, (bool)e.NewValue)));

		/// <summary>
		/// handles the EnableCloseButtonProperty changes.
		/// </summary>
		/// <param name="oldValue">The old value.</param>
		/// <param name="newValue">The new value.</param>
		private void OnEnableCloseButtonChanged(bool oldValue, bool newValue)
		{

		}

		#endregion EnableCloseButton

		#region IsVisible, BringToFront()

		/// <summary>
		/// Wether the floating panel is visible
		/// </summary>
		public bool IsVisible
		{
			get
			{
				return (bool)GetValue(IsVisibleProperty);
			}

			set
			{
				SetValue(IsVisibleProperty, value);
			}
		}

		public static readonly DependencyProperty IsVisibleProperty =
			DependencyProperty.Register(
				"IsVisible",
				typeof(bool),
				typeof(FloatingPanel),
				new PropertyMetadata(true, IsVisiblePropertyChanged));

		/// <summary>
		/// handles the IsVisibleProperty changes.
		/// </summary>
        private static void IsVisiblePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
        {
            var self = sender as FloatingPanel;

            if (args.NewValue != null)
            {
                if ((bool)args.NewValue)
                {
                    //BringToFront();
                    VisualStateManager.GoToState(self, VSMSTATE_StateOpen, true);
                }
                else
                {
                    VisualStateManager.GoToState(self, VSMSTATE_StateClosed, true);
                }
            }
        }


		// TODO fix that!
		// problem: kind of freeze the map, and sometimes move object in the corner!
		void BringToFront()
		{
			return; 
			var p = this.Parent as Panel;
			if (p == null)
				return;
			//var point = Location;
			p.Children.Remove(this);
			p.Children.Add(this);
			//Location = point;
		}

		#endregion IsVisible

		public override void OnApplyTemplate()
		{
			base.OnApplyTemplate();
			contentRoot = (FrameworkElement)GetTemplateChild(PART_ContentRoot);
			root = (Canvas)GetTemplateChild(PART_Root);

		    VisualStateManager.GoToState(this, IsVisible ? VSMSTATE_StateOpen : VSMSTATE_StateClosed, false);
		    Arrange();

			// == event handling below ==
            Application.Current.Host.Content.Resized += (o, e) => Arrange();

			var chrome = (FrameworkElement)GetTemplateChild(PART_Chrome);
			if (chrome != null)
			{
				Point clickPoint = new Point();
				bool isMoving = false;
				chrome.MouseLeftButtonDown += (o, e) =>
				{
					clickPoint = e.GetPosition(contentRoot);
					BringToFront();
					chrome.CaptureMouse();
					isMoving = true;
				};
				chrome.MouseLeftButtonUp += (o, e) =>
				{
					chrome.ReleaseMouseCapture();
					isMoving = false;

                    // Alternatively, persist in the LocationChangedHandler and use RX to throttle.
                    PersistLocation();
				};
				chrome.MouseMove += (o, e) =>
				{
					if (isMoving)
					{
						// If the panel is dragged out of the page, return
						Point position = e.GetPosition(Application.Current.RootVisual);
						if (Application.Current != null &&
							Application.Current.RootVisual != null &&
							(position.X < 0 || position.Y < 0))
						{
							return;
						}

						Point p = e.GetPosition(root);
						Location = new Point(p.X - clickPoint.X, p.Y - clickPoint.Y);
					}
				};
			}

			var resizer = (FrameworkElement)GetTemplateChild(PART_Resizer);
			if (resizer != null)
			{
				Point clickPoint = new Point();
				bool isResizing = false;
				resizer.MouseLeftButtonDown += (o, e) =>
				{
					resizer.CaptureMouse();
					clickPoint = e.GetPosition(o as UIElement);
					isResizing = true;
				};
				resizer.MouseLeftButtonUp += (o, e) =>
				{
					resizer.ReleaseMouseCapture();
					isResizing = false;
				};
				resizer.MouseMove += (o, e) =>
				{
					if (!isResizing)
						return;
			
					// If the panel is resized out of the page, return
					Point position = e.GetPosition(Application.Current.RootVisual);
					if (Application.Current != null 
						&& Application.Current.RootVisual != null 
						&& (position.X < 0 || position.Y < 0))
						return;

					Point p = e.GetPosition(contentRoot);

					if ((p.X > clickPoint.X) && (p.Y > clickPoint.Y))
					{
						contentRoot.Width = p.X - (12 - clickPoint.X);
						contentRoot.Height = p.Y - (12 - clickPoint.Y);
					}
				};
				resizer.MouseEnter += (o, e) =>
				{
					VisualStateManager.GoToState(this, VSMSTATE_StateMouseOver, true);
				};
				resizer.MouseLeave += (o, e) =>
				{
					VisualStateManager.GoToState(this, VSMSTATE_StateNormal, true);
				};
			}

			var closeButton = (Button)GetTemplateChild(PART_CloseButton);
			if (closeButton != null)
			{
				closeButton.Click += (o, e) => IsVisible = false;
			}

            RestoreLocation();
        }

        #region Persist Location

        const string LocationPropertyKey = "Location";
        
        void PersistLocation()
        {
            string name = Persist.GetName(this);

            if (!string.IsNullOrEmpty(name))
            {
                // Attached Property has been set. Save settings;

                Persist.Storage[name, LocationPropertyKey] = SerializeLocation();
            }
        }

        void RestoreLocation()
        {
            string name = Persist.GetName(this);

            if (!string.IsNullOrEmpty(name))
            {
                // Attached Property has been set. Save settings;

                string serialized = Persist.Storage[name, LocationPropertyKey];

                if (!string.IsNullOrEmpty(serialized))
                {
                    Point deser = DeserializeLocation(serialized);

                    if (this.root.ActualWidth != 0)
                    {
                        this.Location = deser;
                    }
                    else
                    {
                        this.InitializationActions.Add(() => this.Location = deser);
                    }
                }
            }
        }        

        private string SerializeLocation()
        {
            return String.Format("{0},{1}", this.Location.X.ToString(), this.Location.Y.ToString());
        }

        private Point DeserializeLocation(string serializedLocation)
        {
            string[] parts = serializedLocation.Split(',');

            return new Point
            {
                X = double.Parse(parts[0]),
                Y = double.Parse(parts[1])
            };
        }
        #endregion
    }
}